require 'webrick'
require 'json'
require 'singleton'
require 'digest/md5'

Struct.new("Message", :from, :to, :body)

def random_session_id
	# Borrowed from Ruby on Rails.
	md5 = Digest::MD5::new
	now = Time.now
	md5.update(now.to_s)
	md5.update(String(now.usec))
	md5.update(String(rand(0)))
	md5.update(String($$))
	md5.update('foobar')
	md5.hexdigest[0,16]
end

class THash
	def initialize
		@h = {}
		@mutex = Mutex.new
	end

	def set(k, v)
		@mutex.synchronize do
			@h[k] = v
		end
	end

	def get(k)
		return @h[k]
	end

	def unset(k)
		@mutex.synchronize do
			if @h.has_key?(k)
				@h.delete(k)
			end
		end
	end

	def each
		@mutex.synchronize do
			@h.each { |k,v| yield k }
		end
	end

	def get_sessions
		s = Array.new
		each { |sid| s.push(sid) }
		return s
	end

	def has?(k)
		@h.has_key?(k)
	end
end

class Room
	def initialize(id)
		@id = id
		@roster = THash.new
		@last_update = Time.now.to_i
	end

	def join(uid)
		@roster.set(uid, true)
		@last_update = Time.now.to_i
	end

	def leave(uid)
		@roster.unset(uid)
		@last_update = Time.now.to_i
	end

	def members
		m = Array.new
		@roster.each { |k| m.push(k) }
		return m
	end

	def send(from, to, message)
		sessions = Sessions.instance

		@roster.each do |m|
			u = sessions.find_user(m)
			r = Struct::Message.new(from, @id, message)
			u.messages << [Time.now.to_i, r]
		end
	end

	attr_reader :last_update, :id
end

class Session
	def initialize(uid, password)
		@uid = uid
		@messages = Queue.new
		@invites = Queue.new
		@room = nil
		@last_poll = 0
		@login_time = Time.now.to_i
	end

	def send(from, to, message)
		r = Struct::Message.new(from, to, message)
		@messages << [Time.now.to_i, r]
	end
	attr_reader :messages, :uid, :login_time, :invites
	attr_accessor :last_poll, :room
end

class Sessions
	include Singleton

	def initialize
		@sessions = THash.new
		@users = THash.new
		@rooms = THash.new
	end

	def remove(sid)
		s = @sessions.get(sid)
		@sessions.unset(sid)
		@users.unset(s.uid)
		if s.room
			@rooms.get(s.room).leave(s.uid)
		end
	end

	def debug_info
		s = ""
		now = Time.now.to_i

		s.concat("Sessions:\n\n")

		@sessions.each do |sid|
			session = @sessions.get(sid)
			s.concat("session #{session.uid}, id #{sid}, connected for #{now - session.login_time} seconds, idle for #{now - session.last_poll} seconds\n")
		end

		s.concat("\nRooms:\n\n")

		@rooms.each do |roomid|
			r = @rooms.get(roomid)
			m = r.members().join(', ')
			s.concat("room #{roomid} has members #{m}\n")
		end

		return s
	end

	def timeout_idle_users
		now = Time.now.to_i
		all = @sessions.get_sessions
		all.each do |sid|
			s = @sessions.get(sid)

			if s.last_poll == 0
				if (now - s.login_time) > 15
					puts "Timeout for #{sid}"
					remove(sid)
				end
			else
				idle = now - s.last_poll

				puts "#{sid} idle for #{idle}"

				if idle > 15
					puts "Timeout for #{sid}"
					remove(sid)
				end
			end
		end
	end


	def join_room(id, uid)
		puts "#{uid} joins #{id}"
		if @rooms.has? id
			r = @rooms.get(id)
			r.join(uid)
		else
			r = Room.new(id)
			r.join(uid)
			@rooms.set(id, r)
		end
	end

	def leave_room(id, uid)
		puts "#{uid} leaves #{id}"
		@rooms.get(id).leave(uid)
	end

	def get_room(id)
		@rooms.get(id)
	end

	def add_session(sid, s)
		throw "User already logged in." if has_user? s.uid
		@sessions.set(sid, s)
		@users.set(s.uid, s)
	end

	def find_target(id)
		t = get_room(id)
		t = find_user(id) if t.nil?
		return t
	end

	def find_user(uid)
		return @users.get(uid)
	end

	def has_user?(uid)
		return false unless uid
		return false unless @users.has? uid
		return true
	end

	def has_session?(sid)
		return false unless sid
		return false unless @sessions.has? sid
		return true
	end

	attr_reader :sessions
end

class LoginServlet < WEBrick::HTTPServlet::AbstractServlet
	def do_GET(req, res)
		unless req.query['uid'] and req.query['password']
			res.body = {:ok => 0, :error => "UID or password missing"}.to_json
			return
		end

		begin
			j = Session.new(req.query['uid'], req.query['password'])

			sid = random_session_id
			puts "sid=", sid
			Sessions.instance.add_session(sid, j)
			res.body = {:ok => 1, :sid => sid}.to_json

			rescue Exception => ex
			res.body = {:ok => 0, :error => "Error: #{ex.to_s}"}.to_json
		end
	end
end

class SessionServlet < WEBrick::HTTPServlet::AbstractServlet
	def do_GET(request, response)
		unless Sessions.instance.has_session?(request.query['sid'])
			response.body = {:ok => 0, :error => "Unknown session."}.to_json
			return
		end

		j = Sessions.instance
		session = j.sessions.get(request.query['sid'])

		status, body = handle_request(request, session)

		response.status = status
		response.body = body.to_json
	end

	def handle_request(request, session)
	end

	alias do_POST do_GET
end

class SendServlet < SessionServlet
	def handle_request(request, session)
		return 200, {:ok => 0, :error => "msg missing"} unless request.query['msg']

		begin
			puts request.query['msg'], "\n"
			m = JSON.parse(request.query['msg'])

			sessions = Sessions.instance

			m['messages'].each do |msg|
				puts "sending msg '", msg['message'], "' to ", msg['to'], "\n"
#	                        r = Struct::Message.new(session.uid, session.uid, msg['to'], msg['message'])

				u = sessions.find_target(msg['to'])
				u.send(session.uid, msg['to'], msg['message'])
			end

			return 200, {:ok => 1, :timestamp => Time.now.to_i}

			rescue => ex
			return 200, {:ok => 0, :error => "JSON parsing failed: #{ex.to_s}"}
		end

	end
end

# invite servlet, will function for both invites to group chat and whiteboard sessions?
class InviteServlet < SessionServlet
	def handle_request(request, session)
	return 200, {:ok => 0, :error => "invite missing"} unless request.query['invite']

		begin
			invite = JSON.parse(request.query['invite'])

			sessions = Sessions.instance

			invite['to'].each do |uid|
				u = sessions.find_user(uid)
				u.invites << {:from => session.uid, :type => invite['type'], :groupid => invite['groupid']}
			end

			return 200, {:ok => 1, :timestamp => Time.now.to_i, :type => invite['type'], :groupid => invite['groupid']}

			rescue => ex
			return 200, {:ok => 0, :error => "JSON parsing failed: #{ex.to_s}"}

		end
	end
end

class PollServlet < SessionServlet
	def handle_request(request, session)

		sessions = Sessions.instance

		if not session.room
			sessions.join_room(request.query['room'], session.uid)
			session.room = request.query['room']
		elsif not session.room == request.query['room']
			sessions.leave_room(session.room, session.uid)
			sessions.join_room(request.query['room'], session.uid)
			session.room = request.query['room']
		end

		room = sessions.get_room(request.query['room'])

		puts "Session last poll: #{session.last_poll}"
		puts "Room last update: #{room.last_update}"

		msg = {}
		msg['ok'] = 1
		msg['messages'] = []
		msg['invites'] = []
		msg['roster'] = []

		while not session.messages.empty?
			(ts,m) = session.messages.pop
			msg['messages'].push({:from => m.from, :to => m.to, :message => m.body, :timestamp => ts})
		end

		while not session.invites.empty?
			puts "about to deliver invite(s?) to #{session.uid}"
			invite = session.invites.pop
			puts "invite= #{invite}"
			msg['invites'].push(invite)
		end

		if session.last_poll <= room.last_update
			msg['roster'] = room.members
		#       session.contacts.each { |c| msg['roster'].push(c) }
		#       session.contacts_modified = false
		end

		session.last_poll = Time.now.to_i

		return 200, msg
	end
end
class JoinServlet < SessionServlet
	def handle_request(request, session)
		return 200, {:ok => 0, :error => "room missing"} unless request.query['room']

		sessions = Sessions.instance
		sessions.join_room(request.query['room'], session.uid)
		return 200, {:ok => 1, :room => request.query['room']}
	end
end

class PartServlet < SessionServlet
	def handle_request(request, session)
		return 200, {:ok => 0, :error => "room missing"} unless request.query['room']

		sessions = Sessions.instance
		sessions.leave_room(request.query['room'], session.uid)
		return 200, {:ok => 1}
	end
end

class LogoutServlet < SessionServlet
	def handle_request(request, session)
		Sessions.instance.remove(request.query['sid'])
		return 200, {:ok => 1}
	end
end

class InfoServlet < WEBrick::HTTPServlet::AbstractServlet
	def do_GET(request, response)
		j = Sessions.instance
		response.body = j.debug_info
		response['Content-Type'] = 'text/plain'
	end
end

s = WEBrick::HTTPServer.new( :Port => 2000 )

s.mount("/login", LoginServlet)
s.mount("/send", SendServlet)
s.mount("/invite", InviteServlet)
s.mount("/join", JoinServlet)
s.mount("/part", PartServlet)
s.mount("/poll", PollServlet)
s.mount("/info", InfoServlet)
s.mount("/logout", LogoutServlet)

trap("INT"){ s.shutdown }

Thread.new do
	loop do
		sleep 15
		puts "Timeout thread running"
		Sessions.instance.timeout_idle_users
	end
end

s.start


